package com.seed.core.builder.query;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryTable;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import com.seed.core.pojo.Const;
import com.seed.core.pojo.seed.Habtms;
import com.seed.core.pojo.seed.HasManys;
import com.seed.core.pojo.seed.Seed;
import com.seed.util.SeedKit;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class FillBuilder extends AbstractQueryBuilder {
    private final String basePath; // self对象的路径
    private final String cross; // self对象下要填充的字段名称
    private final HasManys hasMany;

    public FillBuilder(String basePath, String cross, HasManys hasMany, Seed seed) {
        super(seed);
        this.basePath = basePath;
        this.cross = cross;
        this.hasMany = hasMany;
    }

    public void fill(List<Row> rootList) {
        if(hasMany instanceof Habtms) {
            fillHabtm(rootList);
        } else {
            fillHasMany(rootList);
        }
    }

    private void fillHasMany(List<Row> rootList) {
        List<Row> selfList = getSelfList(rootList, basePath);
        Integer limit = hasMany.getLimit();
        String targetTableAlias = Const.MAIN_TABLE_ALIAS;
        String fieldName = StrUtil.toCamelCase(cross);

        /**
         * 统一查询，然后再根据关联字段分配给每一个self对象
         */
        QueryCondition condition = getHasManyQueryCondition(selfList, targetTableAlias);
        QueryWrapper qw = build().where(condition).limit(limit);
        List<Row> targetList = Db.selectListByQuery(qw);
        fills(targetList);

        String selfField = StrUtil.toCamelCase(hasMany.inferSelfColumn());
        String targetField = StrUtil.toCamelCase(hasMany.inferTargetColumn());
        for(Row self: selfList) {
            List<Row> targetListForSelf = targetList
                    .stream()
                    .filter(target -> target.get(targetField).equals(self.get(selfField)))
                    .toList();
            self.set(fieldName, targetListForSelf);
        }
    }

    private List<Row> getSelfList(List<Row> rootList, String basePath) {
        if(rootList == null || rootList.isEmpty()) return Collections.emptyList();

        if(StrUtil.isBlank(basePath)) return rootList;

        List<Row> ret = new ArrayList<>();
        String[] paths = basePath.split("\\.");
        for(Row root: rootList) {
            Row self = getSelf(root, paths);
            if(self != null) ret.add(self);
        }

        return ret;
    }



    /**
     * 根据根对象获取self对象
     * @param root 根对象
     * @param paths self对象所在路径分隔后的数组，如：user.organization.roles分隔之后： [user, organization, roles]
     * @return self对象
     */
    private Row getSelf(Row root, String... paths) {
        if(root == null) return null;
        if(paths == null || paths.length == 0) return root;

        Row ret = root;
        for (String path: paths) {
            String fieldName = StrUtil.toCamelCase(path);
            ret = (Row) ret.get(fieldName);
            if(ret == null) return null;
        }
        return ret;
    }

    private QueryCondition getHasManyQueryCondition(List<Row> selfList, String targetTableAlias) {
        QueryTable targetTable = new QueryTable(hasMany.targetSeed().getTableName()).as(targetTableAlias);
        String inferTargetColumn = hasMany.inferTargetColumn();

        String selfField = StrUtil.toCamelCase(hasMany.inferSelfColumn());
        QueryCondition ret = new QueryColumn(targetTable, inferTargetColumn).in(getSelfValues(selfList, selfField));
        Map<String, Object> extra = SeedKit.formatExtra(hasMany.getExtraSeed(), hasMany.getExtra());
        for(Map.Entry<String, Object> entry: extra.entrySet()) {
            ret.and(new QueryColumn(targetTable, entry.getKey()).eq(entry.getValue()));
        }

        return ret;
    }

    private List<Object> getSelfValues(List<Row> selfList, String property) {
        List<Object> ret = new ArrayList<>();
        for(Row self: selfList) {
            Object selfValue = self.get(property);
            if(selfValue != null) ret.add(selfValue);
        }

        return ret;
    }

    private void fillHabtm(List<Row> rootList) {
        List<Row> selfList = getSelfList(rootList, basePath);
        String fieldName = StrUtil.toCamelCase(cross);
        Habtms habtm = (Habtms) hasMany;
        String through = habtm.throughTableName();
        Integer limit = hasMany.getLimit();
        String throughAlias = "_";
        for(Row self: selfList) {
            QueryCondition throughCondition = getThroughQueryCondition(self, throughAlias);
            QueryWrapper qw = build().innerJoin(new QueryTable(through).as(throughAlias)).on(throughCondition).limit(limit);
            QueryCondition condition = getQueryCondition(self, throughAlias);
            List<Row> targetList = Db.selectListByQuery(qw.where(condition));
            fills(targetList);
            self.set(fieldName, targetList);
        }
    }

    private QueryCondition getQueryCondition(Row self, String throughAlias) {
        Habtms habtm = (Habtms) hasMany;
        QueryTable targetTable = new QueryTable(habtm.targetSeed().getTableName()).as("t");
        String inferThroughSelfColumn = habtm.inferThroughSelfColumn();
        String inferThroughTargetColumn = habtm.inferThroughTargetColumn();
        String inferTargetColumn = habtm.inferTargetColumn();
        QueryTable throughTable = new QueryTable(habtm.throughTableName()).as(throughAlias);

        String selfField = StrUtil.toCamelCase(habtm.inferSelfColumn());
        Object selfValue = self.get(selfField);
        QueryCondition ret = new QueryColumn(targetTable, inferTargetColumn)
                .eq(new QueryColumn(throughTable, inferThroughTargetColumn))
                .and(new QueryColumn(throughTable, inferThroughSelfColumn).eq(selfValue));
        Map<String, Object> extra = SeedKit.formatExtra(habtm.getExtraSeed(), habtm.getExtra());
        for(Map.Entry<String, Object> entry: extra.entrySet()) {
            ret.and(new QueryColumn(throughTable, entry.getKey()).eq(entry.getValue()));
        }

        return ret;
    }

    private QueryCondition getThroughQueryCondition(Row self, String throughAlias) {
        Habtms habtm = (Habtms) hasMany;
        QueryTable throughTable = new QueryTable(habtm.getThroughSeed().getTableName()).as(throughAlias);
        String inferTargetColumn = habtm.inferTargetColumn();

        String selfField = StrUtil.toCamelCase(habtm.inferSelfColumn());
        QueryCondition ret = new QueryColumn(throughTable, inferTargetColumn).eq(self.get(selfField));
        Map<String, Object> extra = SeedKit.formatExtra(habtm.getExtraSeed(), habtm.getExtra());
        for(Map.Entry<String, Object> entry: extra.entrySet()) {
            ret.and(new QueryColumn(throughTable, entry.getKey()).eq(entry.getValue()));
        }

        return ret;
    }
}
